package com.marketsim.analysis;

import com.marketsim.utils.BigDecimalUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Alex on 03.04.2014.
 */
public class StatisticCalculatorJ8 {


    public static <T> BigDecimal getMean(List<T> data, BigDecimalConverter<T> converter) {
        BigDecimal sum = data.stream().map(converter::convert).reduce(BigDecimal.ZERO, (x, y) -> x.add(y));
        return sum.divide(new BigDecimal(data.size()), BigDecimalUtils.SCALE, RoundingMode.HALF_UP);
    }

    public static BigDecimal getMean(List<BigDecimal> data) {
        BigDecimal sum = data.stream().reduce(BigDecimal.ZERO, (x, y) -> x.add(y));
        return sum.divide(new BigDecimal(data.size()), BigDecimalUtils.SCALE, RoundingMode.HALF_UP);
    }

    public static <T> BigDecimal getVariance(List<T> data, BigDecimalConverter<T> converter) {
        BigDecimal mean = getMean(data, converter);
        BigDecimal temp = BigDecimal.ZERO;
        for (T element : data) {
            BigDecimal bigDecimalElement = converter.convert(element);
            temp = temp.add(mean.subtract(bigDecimalElement).pow(2));
        }
        return temp.divide(new BigDecimal(data.size()), BigDecimalUtils.SCALE, RoundingMode.HALF_UP);
    }

    public static BigDecimal getVariance(List<BigDecimal> data) {
        BigDecimal mean = getMean(data);
        BigDecimal temp = BigDecimal.ZERO;
        for (BigDecimal element : data) {
            temp = temp.add(mean.subtract(element).pow(2));
        }
        return temp.divide(new BigDecimal(data.size()), BigDecimalUtils.SCALE, RoundingMode.HALF_UP);
    }

    public static <T> BigDecimal getStdDev(List<T> data, BigDecimalConverter<T> converter) {
        return new BigDecimal(Math.sqrt(getVariance(data, converter).doubleValue()));
    }

    public static BigDecimal getStdDev(List<BigDecimal> data) {
        return new BigDecimal(Math.sqrt(getVariance(data).doubleValue()));
    }

    public static <T> BigDecimal getSharpRatio(List<T> data, BigDecimalConverter<T> converter) {
        BigDecimal sqrt252 = new BigDecimal(Math.sqrt(252.0d));
        BigDecimal mean = getMean(data, converter);
        BigDecimal stdDev = getStdDev(data, converter);
        return sqrt252.multiply(mean).divide(stdDev, BigDecimalUtils.SCALE, RoundingMode.HALF_UP);
    }

    public static BigDecimal getSharpRatio(List<BigDecimal> data) {
        BigDecimal sqrt252 = new BigDecimal(Math.sqrt(252.0d));
        BigDecimal mean = getMean(data);
        BigDecimal stdDev = getStdDev(data);
        return sqrt252.multiply(mean).divide(stdDev, BigDecimalUtils.SCALE, RoundingMode.HALF_UP);
    }

    public static <T> BigDecimal getMedian(List<T> data, BigDecimalConverter<T> converter) {
        List<BigDecimal> inputList = new ArrayList<>(data.size());
        data.stream().map(converter::convert).sorted().forEach((val) -> inputList.add(val));
        if (inputList.size() % 2 == 0) {
            return (inputList.get((inputList.size() / 2) - 1).add(inputList.get(inputList.size() / 2))).divide(new BigDecimal(2.0));
        } else {
            return inputList.get(inputList.size() / 2);
        }
    }

    public static BigDecimal getMedian(List<BigDecimal> data) {
        List<BigDecimal> inputList = new ArrayList<>(data.size());
        data.stream().sorted().forEach((val) -> inputList.add(val));
        if (inputList.size() % 2 == 0) {
            return (inputList.get((inputList.size() / 2) - 1).add(inputList.get(inputList.size() / 2))).divide(new BigDecimal(2.0));
        } else {
            return inputList.get(inputList.size() / 2);
        }
    }

}
